<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>throttle</title>
  </head>
  <body>
    <input type="text" id="input">
    <script type="text/javascript">
      input.addEventListener('input', throttle(inputTest, 2000), false);

      function inputTest() {
        console.log(this.value);
      }
      /**
       * 英文翻译：
       *  throttle: 节流阀;节流杆;风门;风门杆
       */
      function throttle(fn, delay) {
        // 与防抖类似, 利用闭包储存作用域链获取定时器对象t
        // 不同的是, 在外层函数执行时先记录一次执行时的时间戳
        // 用于后续防抖执行的比较
        var t = null,
          begin = new Date().getTime();

        return function () {
          var _self = this,
            args = arguments,
            now = new Date().getTime();

          if (t) {
            clearTimeout(t);
          }
          // 若当前时间与上次执行的时间差大于延迟
          // 需要正常进行函数执行
          if (now - begin > delay) {
            // 此处本质上可以不使用_self, 
            // 因为此处还是处于内部函数的执行环境
            // this指向仍然是指向调用内部函数的对象
            fn.apply(_self, args);
            // 同时将当前的操作时间替换begin
            begin = now;
          } else {
            // 若没有超过延迟时间, 则重新设定定时器
            t = setTimeout(function() {
              // 刚才没有提到: 因为定时器对象是全局方法, 即全局对象window上的方法
              // 因此定时器内部定义的function函数本质上是独立调用
              // 即this指向的是全局对象, 有兴趣的可以console.log(this)自己验证
              fn.apply(_self, args);
            }, delay);
          }
        };
      }
    </script>
  </body>
</html>
